<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
        .box{width:200px;height:40px;line-height:40px;margin: 50px auto;}
        .box span{display: block;height:40px;border: solid 1px black;}
        .box ul{margin: 0;padding:0;list-style: none;border:solid 1px black;border-top:none;display: none;}
        .box ul li{text-indent: 4px;display: flex;}
        .box ul li.hover{background: #66f;color: #fff;}
        .box ul li#active::after{content:"*";margin-left: auto;margin-right: 20px;}
    </style>
</head>
<body>
    <select>
        <option>上海</option>
        <option>杭州</option>
        <option>南京</option>
        <option>苏州</option>
        <option>北京</option>
        <option>深圳</option>
        <option>广州</option>
    </select>
    <div class="box">
        <span></span>
        <ul>
            <li>上海</li>
            <li>杭州</li>
            <li>南京</li>
            <li>苏州</li>
            <li>北京</li>
            <li>深圳</li>
            <li>广州</li>
        </ul>
    </div>
</body>
<script>

    var ospan = document.querySelector(".box span")
    var oul = document.querySelector(".box ul")
    var ali = oul.children;

    // 1. 设置默认选中项的索引
    var index = 0;
    // 2. 根据默认索引设置默认
    ospan.innerHTML = ali[index].innerHTML;
    ali[index].id = "active";

    // 4. 假设下拉菜单的显示状态：0显示，1隐藏
    var flag = 0;

    // 3. 绑定事件：显示/隐藏
    ospan.onclick = function(eve){
        var e = eve || window.event;
        e.stopPropagation();
        // 5. 根据状态决定显示或隐藏，同时修改状态        
        if(flag === 0){
            oul.style.display = "block";
            flag = 1;
            // 18. 每次打开下拉菜单之前，都需要判断如果不是第一次打开
            // 那么都需要将键盘操作回归到初始值
            if(keyIndex !== undefined){
                ali[keyIndex].className = "";
                keyIndex = undefined;
            }
        }else{
            oul.style.display = "none";
            flag = 0;
        }
    }
    // 6. 点击空白隐藏，同时修改状态（注意事件冒泡）
    document.onclick = function(){
        oul.style.display = "none";
        flag = 0;
    }

    // 7. 解析所有li，并绑定事件
    for(var i=0;i<ali.length;i++){
        // 11. 给每个li绑定索引，以备后用
        ali[i].index = i;
        // 8. 进入
        ali[i].onmouseover = function(){
            // 23. 每次鼠标进入之前，先清除键盘的高亮（有键盘索引）
            if(keyIndex !== undefined){
                ali[keyIndex].className = "";
            }
            // 设置高亮
            this.className = "hover";
            // 20. 鼠标进入时，修改键盘的高亮索引为当前索引
            keyIndex = this.index;
        }
        // 9. 离开
        ali[i].onmouseout = function(){
            // 取消高亮
            // this.className = "";
            // 21. 因为鼠标进入时，绝对会将当前元素的索引设置为键盘控制的索引
            // 所以，取消可能存在的键盘的高亮（替换原有取消鼠标离开时的高亮代码）
            ali[keyIndex].className = "";
            // 22. 因为每次鼠标离开，都会清除高亮，所以为了保证键盘效果的正确，初始化键盘索引
            keyIndex = undefined;
        }
        // 10. 点击
        ali[i].onclick = function(){
            // 将点击的项的内容设置到显示区域
            ospan.innerHTML = this.innerHTML;
            // 取消之前的选中项
            ali[index].id = "";
            // 给当前添加选中项
            this.id = "active";
            // 12. 本次点的 是 下次要取消的
            index = this.index;
        }
    }

    // ---------

    // 键盘

    // 13. 设置键盘高亮索引
    var keyIndex = undefined;

    // 键盘事件
    document.onkeydown = function(eve){
        // 24. 下拉菜单如果没有显示，不应该有键盘事件
        if( flag === 0 ) return ;

        var e = eve || window.event;
        var code = e.keyCode || e.which;
        // 上键
        if(code === 38){
            // 14. 第一个按“上键”
            if(keyIndex === undefined){
                keyIndex = ali.length-1;
            }else
            // 15. 非第一次按“上键”且到达“上键”的终点
            if(keyIndex === 0){
                keyIndex = 0;
            }else{
                // 16. 既不是第一次按“上键”，也没有到达终点
                keyIndex--;
            }
            // 17. 根据键盘的高亮索引，设置样式
            setKeyActive(keyIndex);
        }
        // 下键，同上键
        if(code === 40){
            if(keyIndex === undefined){
                keyIndex = 0;
            }else if(keyIndex === ali.length-1){
                keyIndex = ali.length-1;
            }else{
                keyIndex++;
            }
            setKeyActive(keyIndex);
        }
        // 19. 回车
        if(code === 13){
            // 将键盘选中的项的文字设置到显示区域
            ospan.innerHTML = ali[keyIndex].innerHTML;
            // 取消之前选中的项的标志（*）
            ali[index].id = "";
            // 给当前选中的项添加标志（*）
            ali[keyIndex].id = "active";
            // 将当前选中的项的索引设置成下次回车要取消的索引
            index = keyIndex;
            // 隐藏下拉菜单
            oul.style.display = "none";
            // 同步状态
            flag = 0;
        }
    }



    
    function setKeyActive(now){
        for(var i=0;i<ali.length;i++){
            ali[i].className = "";
        }
        ali[now].className = "hover"
    }



</script>
</html>